@file:OptIn(ExperimentalForeignApi::class)

package com.tencent.compose.utils

import androidx.compose.ui.napi.JsEnv
import kotlinx.cinterop.*
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.withTimeoutOrNull
import platform.ohos.*
import platform.napiwrapper.kn_observe_promise_jsvalue
import platform.test725.*
import kotlin.experimental.ExperimentalNativeApi
import kotlin.time.measureTime

private const val DEFAULT_PROMISE_DELAY_MS = 8000
private const val QUICK_PROMISE_DELAY_MS = 2000
private const val SEMAPHORE_TIMEOUT_MS = 10_000

private data class PromiseObserver(
    val deferred: CompletableDeferred<Boolean>,
    val callback: ((Boolean) -> Unit)?
)

private fun callDemoPromise(delayMs: Int = DEFAULT_PROMISE_DELAY_MS): napi_value? {
    val global = TsEnv.getGlobal()
    val funcName = TsEnv.createStringUtf8("demoLongTask")
    val func = TsEnv.getProperty(global, funcName)
    if (func == null) {
        println("Kotlin: demoLongTask is not defined on globalThis")
        return null
    }
    val delayArg = TsEnv.createInt32(delayMs)
    return TsEnv.callFunction(global, func, delayArg)
}

private fun callDemoSyncTask(): Boolean {
    val global = TsEnv.getGlobal()
    val funcName = TsEnv.createStringUtf8("demoSyncTask")
    val func = TsEnv.getProperty(global, funcName)
    if (func == null) {
        println("Kotlin: demoSyncTask is not defined on globalThis")
        return false
    }
    val result = TsEnv.callFunction(global, func)
    return TsEnv.getValueBool(result) ?: false
}

private fun observePromise(
    promise: napi_value?,
    onResult: ((Boolean) -> Unit)? = null
): CompletableDeferred<Boolean> {
    val deferred = CompletableDeferred<Boolean>()
    val value = promise
    if (value == null) {
        deferred.complete(false)
        onResult?.invoke(false)
        return deferred
    }
    val observer = PromiseObserver(deferred, onResult)
    val ref = StableRef.create(observer)
    val handler = staticCFunction { valuePtr: napi_value?, data: COpaquePointer? ->
        val observerRef = data?.asStableRef<PromiseObserver>()
        observerRef?.let { payloadRef ->
            val payload = payloadRef.get()
            val boolValue = valuePtr?.let { TsEnv.getValueBool(it) } ?: false
            payload.callback?.invoke(boolValue)
            if (!payload.deferred.isCompleted) {
                payload.deferred.complete(boolValue)
            }
            payloadRef.dispose()
        }
        Unit
    }
    kn_observe_promise_jsvalue(TsEnv.env(), value, handler, ref.asCPointer())
    return deferred
}

private fun stringParamBeforeOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    val params = JsEnv.getCbInfo(info).arguments
//    val params = info!!.params(1)
//    val arg0 = params[0]!!.asString()
    var arg0: String?
    print_time("kt, stringParamBeforeOpt, tag1".cstr)
    val duration = measureTime {
        arg0 = JsEnv.getValueStringUtf8(params[0])
    }
    print_time("kt, stringParamBeforeOpt, tag2".cstr)
    println("KN: stringParamBeforeOpt time=${duration.inWholeNanoseconds} ns")
    return null
}

private fun stringParamAfterOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    val params = JsEnv.getCbInfo(info).arguments
//    val params = info!!.params(1)
    var arg0: String?
    print_time("kt, stringParamAfterOpt, tag1".cstr)
    val duration = measureTime {
        arg0 = params[0]!!.asString()
    }
    print_time("kt, stringParamAfterOpt, tag2".cstr)
    println("KN: stringParamAfterOpt time=${duration.inWholeNanoseconds} ns")
    return null
}

private fun stringResultBeforeOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    val res: napi_value?
    val duration = measureTime {
        res = JsEnv.createStringUtf8(TestString)
    }
    println("KN: stringResultBeforeOpt time=${duration.inWholeNanoseconds} ns")
    return res
}

private fun stringResultAfterOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    var res: napi_value?
    val duration = measureTime {
        res = createString(TestString)
    }
    println("KN: stringResultAfterOpt time=${duration.inWholeNanoseconds} ns")
    return res
}

private fun intArrayParamBeforeOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    val params = JsEnv.getCbInfo(info).arguments
    val jsValue = params[0]
    val length = JsEnv.getArrayLength(jsValue)
    val duration = measureTime {
        repeat(length) { index ->
            val itemValue = getElementInArray(env, jsValue, index)
            val numInt32 = JsEnv.getValueInt32(itemValue)
        }
    }
    println("KN: intArrayParamBeforeOpt time=${duration.inWholeNanoseconds} ns")
    return null
}

private fun intArrayParamAfterOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    val params = JsEnv.getCbInfo(info).arguments
    val jsValue = params[0]
    val length = JsEnv.getArrayLength(jsValue)
    val duration = measureTime {
        repeat(length) { index ->
            val itemValue = getElementInArray(env, jsValue, index)
//        val numInt32 = JsEnv.getValueInt32(itemValue)
            val numInt32 = napi_get_value_kotlin_int32(env.rawValue, itemValue.rawValue)
        }
    }
    println("KN: intArrayParamAfterOpt time=${duration.inWholeNanoseconds} ns")
    return null
}

private fun intArrayResultBeforeOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    var array: napi_value?
    val duration = measureTime {
        val size = TestIntArray.size
        array = createArrayWithLength(env, size)
        repeat(size) { index ->
//            val value = napi_create_kotlin_int32(env.rawValue, TestIntArray[index])
//            setElementInArray(env.rawValue, array.rawValue, index, value)
            val value = JsEnv.createInt32(TestIntArray[index])
            JsEnv.setElement(array, index, value)
        }
    }
    println("KN: intArrayResultBeforeOpt time=${duration.inWholeNanoseconds} ns")
    return array
}

private fun intArrayResultAfterOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    var array: napi_value?
    val duration = measureTime {
        val size = TestIntArray.size
        array = createArrayWithLength(env, size)
        repeat(size) { index ->
            val value: napi_value? = interpretCPointer(napi_create_kotlin_int32(env.rawValue, TestIntArray[index]))
            setElementInArray(env, array, index, value)
        }
    }
    println("KN: intArrayResultAfterOpt time=${duration.inWholeNanoseconds} ns")
    return array
}

/// stringArray

private fun stringArrayParamBeforeOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    val params = JsEnv.getCbInfo(info).arguments
    val jsValue = params[0]
    val length = JsEnv.getArrayLength(jsValue)
    val duration = measureTime {
        repeat(length) { index ->
           val itemValue = getElementInArray(env, jsValue, index)
           val itemStr = JsEnv.getValueStringUtf8(itemValue)
        }
    }
    println("KN: stringArrayParamBeforeOpt time=${duration.inWholeNanoseconds} ns")
    return null
}

private fun stringArrayParamAfterOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    val params = JsEnv.getCbInfo(info).arguments
    val jsValue = params[0]
    val length = JsEnv.getArrayLength(jsValue)
    val duration = measureTime {
        repeat(length) { index ->
           val itemValue = getElementInArray(env, jsValue, index)
//        val itemStr = JsEnv.getValueStringUtf8(itemValue) // 优化前
           val itemStr = itemValue?.asString()
        }
    }
    println("KN: stringArrayParamAfterOpt time=${duration.inWholeNanoseconds} ns")
    return null
}

private fun stringArrayResultBeforeOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    var array: napi_value?
    val duration = measureTime {
        val size = TestStringArray.size
        array = createArrayWithLength(env, size)
        repeat(size) { index ->
            val value = JsEnv.createStringUtf8(TestStringArray[index])
            JsEnv.setElement(array, index, value)
        }
    }
    println("KN: stringArrayResultBeforeOpt time=${duration.inWholeNanoseconds} ns")
    return array
}

private fun stringArrayResultAfterOpt(env: napi_env?, info: napi_callback_info?): napi_value? {
    var array: napi_value?
    val duration = measureTime {
        val size = TestStringArray.size
        array = createArrayWithLength(env, size)
        repeat(size) { index ->
            val value = createString(TestStringArray[index])
            setElementInArray(env, array, index, value)
        }
    }
    println("KN: stringArrayResultAfterOpt time=${duration.inWholeNanoseconds} ns")
    return array
}

/// async demos

private fun testAsyncWithCallback(env: napi_env?, info: napi_callback_info?): napi_value? {
    println("Kotlin: testAsyncWithCallback invoked - using callback style")
    val promise = callDemoPromise(QUICK_PROMISE_DELAY_MS) ?: return TsEnv.getBoolean(false)
    observePromise(promise) { result ->
        println("Kotlin: callback received promise result=$result")
    }
    println("Kotlin: callback registered, returning immediately")
    return TsEnv.getBoolean(true)
}

private fun testAsyncWithRunBlockingCrash(env: napi_env?, info: napi_callback_info?): napi_value? {
    println("Kotlin: testAsyncWithRunBlockingCrash invoked - runBlocking will block UI thread")
    val promise = callDemoPromise() ?: return TsEnv.getBoolean(false)
    val deferred = observePromise(promise)
    val result = runBlocking {
        println("Kotlin: runBlocking waiting for ArkTS promise result...")
        deferred.await()
    }
    println("Kotlin: runBlocking completed with result=$result")
    return TsEnv.getBoolean(result)
}

private fun testAsyncWithCoroutineScope(env: napi_env?, info: napi_callback_info?): napi_value? {
    println("Kotlin: testAsyncWithCoroutineScope invoked")
    val promise = callDemoPromise(QUICK_PROMISE_DELAY_MS) ?: return TsEnv.getBoolean(false)
    val deferred = observePromise(promise)
    GlobalScope.launch {
        val result = deferred.await()
        println("Kotlin: coroutine scope received promise result=$result")
    }
    println("Kotlin: coroutine launched, returning immediately")
    return TsEnv.getBoolean(true)
}

private fun testAsyncWithFuture(env: napi_env?, info: napi_callback_info?): napi_value? {
    println("Kotlin: testAsyncWithFuture invoked - using async/await")
    val promise = callDemoPromise(QUICK_PROMISE_DELAY_MS) ?: return TsEnv.getBoolean(false)
    val deferred = observePromise(promise)
    val future = GlobalScope.async {
        println("Kotlin: async awaiting promise result...")
        deferred.await()
    }
    GlobalScope.launch {
        try {
            val value = future.await()
            println("Kotlin: async result received: $value")
        } catch (t: Throwable) {
            println("Kotlin: async result failed: ${t.message}")
        }
    }
    println("Kotlin: async scheduled, returning immediately")
    return TsEnv.getBoolean(true)
}

private fun testAsyncWithChannel(env: napi_env?, info: napi_callback_info?): napi_value? {
    println("Kotlin: testAsyncWithChannel invoked")
    val promise = callDemoPromise(QUICK_PROMISE_DELAY_MS) ?: return TsEnv.getBoolean(false)
    val channel = Channel<Boolean>(capacity = 1)
    observePromise(promise) { result ->
        println("Kotlin: promise resolved, sending through channel: $result")
        GlobalScope.launch {
            channel.send(result)
            channel.close()
        }
    }
    GlobalScope.launch {
        for (value in channel) {
            println("Kotlin: channel received result=$value")
        }
    }
    println("Kotlin: channel listeners registered, returning immediately")
    return TsEnv.getBoolean(true)
}

private fun testAsyncWithSemaphore(env: napi_env?, info: napi_callback_info?): napi_value? {
    println("Kotlin: testAsyncWithSemaphore invoked - coroutine + semaphore demo")
    val promise = callDemoPromise(QUICK_PROMISE_DELAY_MS) ?: return TsEnv.getBoolean(false)
    val deferred = observePromise(promise)
    val semaphore = Semaphore(permits = 1, acquiredPermits = 1)
    var promiseResult = false

    GlobalScope.launch {
        try {
            promiseResult = deferred.await()
            println("Kotlin: semaphore producer observed promise result=$promiseResult")
        } catch (t: Throwable) {
            println("Kotlin: semaphore producer failed: ${t.message}")
        } finally {
            semaphore.release()
        }
    }

    GlobalScope.launch {
        val acquired = semaphore.acquireWithin(SEMAPHORE_TIMEOUT_MS.toLong())
        if (acquired) {
            println("Kotlin: semaphore acquired, final result=$promiseResult")
        } else {
            println("Kotlin: semaphore acquire timeout, cancel pending promise")
            deferred.cancel()
        }
    }

    println("Kotlin: semaphore wiring completed, returning immediately")
    return TsEnv.getBoolean(true)
}

private fun testSyncCall(env: napi_env?, info: napi_callback_info?): napi_value? {
    println("Kotlin: testSyncCall invoked")
    val result = callDemoSyncTask()
    println("Kotlin: sync call result=$result")
    return TsEnv.getBoolean(result)
}

private fun fooObjTest(env: napi_env?, info: napi_callback_info?): napi_value? {
    val params = JsEnv.getCbInfo(info).arguments
    val jsValue = params[0]
    val foo = JsFoo.toFooObject(jsValue)
    if (foo != null) {
        foo.str1Value = "kotlin reset value"
//        foo.str9Value = "kotlin9"
    }
    return JsFoo.toNapiValue(foo)
}

// define function
@CName("defineFfiFunctions")
@OptIn(ExperimentalNativeApi::class)
fun defineFfiFunctions(env: napi_env, exports: napi_value?) {
    JsEnv.init(env)
    TsEnv.init(env)
    val descArray = nativeHeap.allocArray<napi_property_descriptor>(20)
    var index = 0;
    descArray[index++].apply {
        name = createString("stringParamBeforeOpt")
        method = staticCFunction(::stringParamBeforeOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("stringParamAfterOpt")
        method = staticCFunction(::stringParamAfterOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("stringResultBeforeOpt")
        method = staticCFunction(::stringResultBeforeOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("stringResultAfterOpt")
        method = staticCFunction(::stringResultAfterOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("intArrayParamBeforeOpt")
        method = staticCFunction(::intArrayParamBeforeOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("intArrayParamAfterOpt")
        method = staticCFunction(::intArrayParamAfterOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("intArrayResultBeforeOpt")
        method = staticCFunction(::intArrayResultBeforeOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("intArrayResultAfterOpt")
        method = staticCFunction(::intArrayResultAfterOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("stringArrayParamBeforeOpt")
        method = staticCFunction(::stringArrayParamBeforeOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("stringArrayParamAfterOpt")
        method = staticCFunction(::stringArrayParamAfterOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("stringArrayResultBeforeOpt")
        method = staticCFunction(::stringArrayResultBeforeOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("stringArrayResultAfterOpt")
        method = staticCFunction(::stringArrayResultAfterOpt)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("fooObjTest")
        method = staticCFunction(::fooObjTest)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("testAsyncWithCallback")
        method = staticCFunction(::testAsyncWithCallback)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("testAsyncWithRunBlockingCrash")
        method = staticCFunction(::testAsyncWithRunBlockingCrash)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("testAsyncWithCoroutineScope")
        method = staticCFunction(::testAsyncWithCoroutineScope)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("testAsyncWithFuture")
        method = staticCFunction(::testAsyncWithFuture)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("testAsyncWithChannel")
        method = staticCFunction(::testAsyncWithChannel)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("testAsyncWithSemaphore")
        method = staticCFunction(::testAsyncWithSemaphore)
        attributes = napi_default
    }
    descArray[index++].apply {
        name = createString("testSyncCall")
        method = staticCFunction(::testSyncCall)
        attributes = napi_default
    }
    
    val res = napi_define_properties(env, exports, index.toULong(), descArray)
    println("KN: defineFfiFunctions, res=$res")
}

private suspend fun Semaphore.acquireWithin(timeoutMs: Long): Boolean {
    return withTimeoutOrNull(timeoutMs) {
        acquire()
        true
    } ?: false
}
